/**
 * 
 */
package com.macasa.panel.common.log;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import com.macasa.panel.common.log.annotations.ILoggable;

/**
 * Class LoggableImpl
 * <P>
 * Spring aop log injection.
 * 
 * @author Alexis Sorbier
 * @date 12 septembre 2011
 */
@Aspect
@Component
public class LoggableImpl {

	/**
	 * Pointcut definition for any methods
	 * <P>
	 */
	@Pointcut(value = "execution(* *(..))")
	public void anyMethods() {}

	/**
	 * Get the logger from target class of ProceedingJoinPoint
	 * <P>
	 * 
	 * @param pjp
	 *            ProceedingJoinPoint
	 * @return logger Logger
	 */
	private Log getLog(final ProceedingJoinPoint pjp) {
		return LogFactory.getLog(pjp.getTarget().getClass());
	}

	/**
	 * Format method exit log
	 * <P>
	 * 
	 * @param pjp
	 *            ProceedingJoinPoint
	 * @param loggable
	 *            ILoggable
	 * @param retValue
	 *            Object
	 * @param start
	 *            long
	 * @return result String
	 */
	private String logAfter(final ProceedingJoinPoint pjp, final Object retValue, final long start) {
		final StringBuilder builder = new StringBuilder(pjp.getSignature().toShortString());
		builder.append(" EXIT return ").append(retValue == null ? "null or void" : retValue.toString()).append(" in ");
		builder.append(System.currentTimeMillis() - start).append(" ms");
		return builder.toString();
	}

	/**
	 * Log around any methods @ILoggabble
	 * <P>
	 * 
	 * @param pjp
	 *            ProceedingJoinPoint
	 * @param loggable
	 *            ILoggable
	 * @throws Throwable
	 */
	@Around("anyMethods() && @annotation(loggable)")
	public Object logAround(final ProceedingJoinPoint pjp, final ILoggable loggable) throws Throwable {
		final long start = System.currentTimeMillis();
		final Log log = this.getLog(pjp);
		if (log.isInfoEnabled()) {
			if (!StringUtils.isEmpty(loggable.comment())) {
				log.info(loggable.comment());
			}
			log.info(this.logBefore(pjp));
		}
		Object retValue = null;
		try {
			retValue = pjp.proceed();
		} catch (final Throwable th) {
			if (log.isErrorEnabled()) {
				log.error(th.getMessage());
			}
			throw th;
		} finally {
			if (log.isInfoEnabled()) {
				log.info(this.logAfter(pjp, retValue, start));
			}
		}
		return retValue;
	}

	/**
	 * Format method entry log
	 * <P>
	 * 
	 * @param pjp
	 *            ProceedingJoinPoint
	 * @return result String
	 */
	private String logBefore(final ProceedingJoinPoint pjp) {
		final Object[] args = pjp.getArgs();
		final StringBuilder builder = new StringBuilder(pjp.getSignature().toShortString());
		builder.append(" ENTRY ");
		builder.append("called with args: [");
		if (args != null) {
			for (int i = 0; i < args.length; i++) {
				builder.append("'" + args[i] + "'");
				builder.append((i == (args.length - 1)) ? "" : ", ");
			}
		}
		builder.append("]");
		return builder.toString();
	}
}
